#include "dpi.h"

const char *regs[] = {
  "$0", "ra", "sp", "gp", "tp", "t0", "t1", "t2",
  "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5",
  "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7",
  "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"
};
int vmem_cnt = 0;
#define KEYDOWN_MASK 0x8000
typedef struct {
    int state;
    uint64_t halt_pc;
    uint32_t halt_ret;
} NPCState;
enum { NPC_RUNNING, NPC_STOP, NPC_END, NPC_ABORT, NPC_QUIT };
int ebreak=0;
uint64_t *cpu_gpr = NULL;
uint32_t *pc_gpr = NULL;
extern NPCState npc_state;
extern uint8_t pc_reg[0x8000000];
static uint64_t boot_time = 0;
extern uint64_t timer ;
////////////////////////////////////////////////////////////
/////         VGA
////////////////////////////////////////////////////////////
#ifdef VGA
#define SCREEN_W 400
#define SCREEN_H 300
#define SCREEN_WH 120000
static SDL_Renderer *renderer = NULL;
static SDL_Texture *texture = NULL;
static void *vmem = NULL;
 uint32_t screen_size() {
  return SCREEN_WH * sizeof(uint32_t);
}
 void init_screen() {
	SDL_Window *window = NULL;
	char title[128];
	sprintf(title, "riscv64-npc");
	SDL_Init(SDL_INIT_VIDEO);
	SDL_CreateWindowAndRenderer(
		SCREEN_W ,
		SCREEN_H ,
		0, &window, &renderer);
	SDL_SetWindowTitle(window, title);
	texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888,
		SDL_TEXTUREACCESS_STATIC, SCREEN_W, SCREEN_H);
	vmem = malloc(screen_size());
	memset(vmem, 0, screen_size());
}

static inline void update_screen() {
  SDL_UpdateTexture(texture, NULL, vmem, SCREEN_W * sizeof(uint32_t));
  SDL_RenderClear(renderer);
  SDL_RenderCopy(renderer, texture, NULL, NULL);
  SDL_RenderPresent(renderer);
}
void vga_update_screen() {
    update_screen();
}
#endif
////////////////////////////////////////////////////////////
////////////////////////key
////////////////////////////////////////////////////////////
#ifdef KEYBOARD
#define concat_temp(x, y) x ## y
#define concat(x, y) concat_temp(x, y)
#define _KEYS(f) \
  f(ESCAPE) f(F1) f(F2) f(F3) f(F4) f(F5) f(F6) f(F7) f(F8) f(F9) f(F10) f(F11) f(F12) \
f(GRAVE) f(1) f(2) f(3) f(4) f(5) f(6) f(7) f(8) f(9) f(0) f(MINUS) f(EQUALS) f(BACKSPACE) \
f(TAB) f(Q) f(W) f(E) f(R) f(T) f(Y) f(U) f(I) f(O) f(P) f(LEFTBRACKET) f(RIGHTBRACKET) f(BACKSLASH) \
f(CAPSLOCK) f(A) f(S) f(D) f(F) f(G) f(H) f(J) f(K) f(L) f(SEMICOLON) f(APOSTROPHE) f(RETURN) \
f(LSHIFT) f(Z) f(X) f(C) f(V) f(B) f(N) f(M) f(COMMA) f(PERIOD) f(SLASH) f(RSHIFT) \
f(LCTRL) f(APPLICATION) f(LALT) f(SPACE) f(RALT) f(RCTRL) \
f(UP) f(DOWN) f(LEFT) f(RIGHT) f(INSERT) f(DELETE) f(HOME) f(END) f(PAGEUP) f(PAGEDOWN)

#define _KEY_NAME(k) _KEY_##k,
#define MAP(c, f) c(f)
enum {
  _KEY_NONE = 0,
  MAP(_KEYS, _KEY_NAME)
};

#define SDL_KEYMAP(k) keymap[concat(SDL_SCANCODE_, k)] = concat(_KEY_, k);
static uint32_t keymap[256] = {};

void init_keymap() {
  MAP(_KEYS, SDL_KEYMAP)
}

#define KEY_QUEUE_LEN 1024
static int key_queue[KEY_QUEUE_LEN] = {};
static int key_f = 0, key_r = 0;

static void key_enqueue(uint32_t am_scancode) {
  key_queue[key_r] = am_scancode;
  key_r = (key_r + 1) % KEY_QUEUE_LEN;
}

static uint32_t key_dequeue() {
  uint32_t key = _KEY_NONE;
  if (key_f != key_r) {
    key = key_queue[key_f];
    key_f = (key_f + 1) % KEY_QUEUE_LEN;
  }
  return key;
}

void send_key(uint8_t scancode, bool is_keydown) {
  if (npc_state.state == NPC_RUNNING && keymap[scancode] != _KEY_NONE) {
    uint32_t am_scancode = keymap[scancode] | (is_keydown ? KEYDOWN_MASK : 0);
    key_enqueue(am_scancode);
  }
}
#endif
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
//			time
////////////////////////////////////////////////////////////
// static uint64_t get_time_internal() {
//   struct timeval now;
//   gettimeofday(&now, NULL);
//   uint64_t us = now.tv_sec * 1000000 + now.tv_usec;
//   return us;
// }
// uint64_t get_time() {
//   if (boot_time == 0) boot_time = get_time_internal();
//   uint64_t now = get_time_internal();
//   return now - boot_time;
// }
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////


extern "C" void set_break(const svLogicVecVal* a0){
	
	if (!cpu_gpr[10])
	{
		npc_state.state = NPC_END;
		printf(ANSI_FG_GREEN "test pass\n" ANSI_NONE);
	}
	else{
		npc_state.state = NPC_ABORT;
		printf(ANSI_FG_RED "test fialed\n" ANSI_NONE);
	}
}
extern "C" void set_gpr_ptr(const svOpenArrayHandle r) {
  	cpu_gpr = (uint64_t *)(((VerilatedDpiOpenVar*)r)->datap());
}
void isa_reg_display() {
	int i;
	for (i = 0; i < 32; i++) {
		printf("%s = 0x%lx\n",regs[i], cpu_gpr[i]);
	}
}
extern "C" void get_inst (const svOpenArrayHandle r){
	pc_gpr = (uint32_t *)(((VerilatedDpiOpenVar*)r)->datap());
#ifdef DISPLAY
	printf("inst = 0x%x\n",pc_gpr[0]);
	printf("pc = 0x%x\n",pc_gpr[1]);
	printf("nxpc = 0x%x\n",pc_gpr[2]);
#endif
}
// extern uint32_t *Vstop_cnt;
// extern "C" void get_stop_cnt (const svOpenArrayHandle r){
// 	Vstop_cnt = (uint32_t *)(((VerilatedDpiOpenVar*)r)->datap());
// }
#define CONFIG_I8042_DATA_MMIO 0xa0000060
uint8_t* guest_to_host(uint64_t paddr) { return pc_reg + paddr - 0x80000000; }
uint64_t host_to_guest(uint8_t *haddr) { return haddr - pc_reg +  0x80000000; }
static inline word_t host_read(void *addr, int len) {
  switch (len) {
    case 0x1: return *(uint8_t  *)addr;
    case 0x3: return *(uint16_t *)addr;
    case 0x7: return *(uint32_t *)addr;
    case 0xf: return *(uint64_t *)addr;
    default: return 0;
  }
}

static inline void host_write(void *addr, int len, word_t data) {
  switch (len) {
    case 0x1: *(uint8_t  *)addr = data; return;
    case 0x3: *(uint16_t *)addr = data; return;
    case 0x7: *(uint32_t *)addr = data; return;
    case 0xf: *(uint64_t *)addr = data; return;
    default: assert(0);
  }
}
extern "C" void pmem_read(long long raddr, long long *rdata, char rmask) {
	uint64_t read_addr = (raddr&0xffffffff);
	uint64_t us=0;
#ifdef MTRACE 
	printf("timer = %ld raddr = 0x%llx ",timer,raddr);
#endif
	if (rmask != 0)
	{
		if (((read_addr)==0xa0000048))
		{
			// us = get_time()&0xffffffff;
			// *rdata = us;
			*rdata =( timer>>3) &0xffffffff;
		}
		else if(read_addr==0xa000004C)
		{
			*rdata = timer >> 35;
			// us = get_time()>>32;
			// *rdata = us;
		}
		#ifdef KEYBOARD
		else if(read_addr==CONFIG_I8042_DATA_MMIO)
		{
			*rdata = key_dequeue();
		}
		#endif
		#ifdef VGA
		else if (read_addr==0xa0000100)
		{
			*rdata = 300;
		}
		else if (read_addr==0xa0000102)
		{
			*rdata = 400;
		}
		
		else if (read_addr==0xa0000108)
		{
			*rdata = 0;
		}
		#endif
		else if(read_addr >= 0x80000000 || read_addr < 0x88000000)
		{
			*rdata = host_read((guest_to_host(raddr)),rmask);
		}
		else {
			printf("error read addr\n");
			printf("timer = %ld raddr = 0x%llx \n",timer,raddr);
		}
		
	}
#ifdef MTRACE 
  	printf("  rdata = 0x%llx\n",*rdata);
#endif
	
}
extern "C" void pmem_write(long long waddr, long long wdata, char wmask) {
  // 总是往地址为`waddr & ~0x7ull`的8字节按写掩码`wmask`写入`wdata`
  // `wmask`中每比特表示`wdata`中1个字节的掩码,
  // 如`wmask = 0x3`代表只写入最低2个字节, 内存中的其它字节保持不变
#ifdef MTRACE 
  	printf("timer = %ld  waddr = 0x%llx wdata = 0x%llx\n",timer ,waddr,wdata);
#endif
	if ((waddr&0xffffffff)==0xa00003f8)
	{
		uint8_t data_acssi = wdata&0xff;
		printf("%c",data_acssi);
	}
#ifdef VGA
	else if ((waddr&0xffffffff)==0xa0000104)
	{
		if(wdata &1 >0){
			vga_update_screen();
		}
	}
	else if ((waddr&0xff000000)==0xa1000000 &&(waddr-0xa1000000 < SCREEN_WH * sizeof(uint32_t)))
	{
		// printf("vmem_cnt = %d  timer = %ld addr = 0x%llx ,data = 0x%llx\n",vmem_cnt,timer,waddr,wdata);
		
		switch (wmask )
		{
			case 0x1:*(uint8_t *)((uint8_t *)vmem+(waddr-0xa1000000)) = wdata;
				break;
			case 0x3:*(uint16_t *)((uint8_t *)vmem+(waddr-0xa1000000)) = (uint16_t)wdata;
				break;
			case 0x7:*(uint32_t *)((uint8_t *)vmem+(waddr-0xa1000000)) = (uint32_t)wdata;
				break;
			case 0xf:*(uint64_t *)((uint8_t *)vmem+(waddr-0xa1000000)) = wdata;
				break;
			default:
				break;
		}
		// // case 4: *(uint32_t *)addr = data;
		// vmem+(waddr-0xa1000000)
	}
#endif	
	
	else if(waddr>=0x80000000 || waddr<0x88000000)
	{
		host_write(guest_to_host(waddr), wmask, wdata);
	}
	else {
		printf("error wirte addr\n");
	}
}